package sk.stuba.fiit.repka.dp.owl.gui;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.border.TitledBorder;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.apache.log4j.Logger;

import sk.stuba.fiit.repka.dp.db.DBRatingManipulator;
import sk.stuba.fiit.repka.dp.db.DataManipulator;
import sk.stuba.fiit.repka.dp.db.EnumUtils;
import sk.stuba.fiit.repka.dp.gui.AddInputDialog;
import sk.stuba.fiit.repka.dp.gui.MyButton;
import sk.stuba.fiit.repka.dp.gui.RecommendationsDialog;
import sk.stuba.fiit.repka.dp.owl.MyOWLClass;
import sk.stuba.fiit.repka.dp.owl.MyOWLObjectProperty;
import sk.stuba.fiit.repka.dp.owl.OWLComponent;
import sk.stuba.fiit.repka.dp.owl.OWLRestriction;

public class ClazzPanel extends JPanel {
	private static Logger logger = Logger.getLogger(ClazzPanel.class);
	
	private JTree tree;
	private DefaultMutableTreeNode top;
	private DefaultTreeModel treeModel;
	private MainPanel parent;
	
	private JPanel disjoinPanel; 
	private Map<MyDeleteBT, MyOWLClass> disjoinMapper;
	private JPanel restrictionPanel;
	private Map<MyDeleteBT, OWLRestriction> restrictionMapper;

	/**
	 * Create the panel.
	 */
	public ClazzPanel(MainPanel parent) {
		this.parent = parent;
		this.initialize();
	}
	
	protected void setPanelLayout() {
		GridBagLayout gridBagLayout = new GridBagLayout();
		gridBagLayout.columnWidths = new int[]{28, 29, 0, 0};
		gridBagLayout.rowHeights = new int[]{198, 0, 0, 0};
		gridBagLayout.columnWeights = new double[]{0.0, 1.0, 1.0, Double.MIN_VALUE};
		gridBagLayout.rowWeights = new double[]{1.0, 1.0, 1.0, Double.MIN_VALUE};
		setLayout(gridBagLayout);
	}
	
	protected JToolBar setToolBar() {
		JToolBar toolBar = new JToolBar();
		toolBar.setOrientation(SwingConstants.VERTICAL);
		GridBagConstraints gbc_toolBar = new GridBagConstraints();
		gbc_toolBar.gridheight = 3;
		gbc_toolBar.insets = new Insets(0, 0, 5, 5);
		gbc_toolBar.fill = GridBagConstraints.BOTH;
		gbc_toolBar.gridx = 0;
		gbc_toolBar.gridy = 0;
		add(toolBar, gbc_toolBar);
		return toolBar;
	}
	
	protected void setToolBarButtons(JToolBar toolBar) {
		GridBagLayout gridBagLayout = new GridBagLayout();
		gridBagLayout.columnWidths = new int[]{0, 0};
		gridBagLayout.rowHeights = new int[]{0, 0, 0, 0, 0, 0};
		gridBagLayout.columnWeights = new double[]{0.0, Double.MIN_VALUE};
		gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
		toolBar.setLayout(gridBagLayout);
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.insets = new Insets(10, 0, 0, 0);
		
		MyButton addBT = new MyButton("Add");
		addBT.addActionListener(new AddBTActionListener(this));
		gbc.gridx = 0;
		gbc.gridy = 0;
		toolBar.add(addBT, gbc);
		
		MyButton removeBT = new MyButton("Remove");
		removeBT.addActionListener(new RemoveBTActionListener(this));
		gbc.gridx = 0;
		gbc.gridy = 1;
		toolBar.add(removeBT, gbc);
		
		MyButton disjoinBT = new MyButton("Disjoin With");
		disjoinBT.addActionListener(new DisjoinBTActionListener(this));
		gbc.gridx = 0;
		gbc.gridy = 2;
		toolBar.add(disjoinBT, gbc);
		
		MyButton restrictionBT = new MyButton("Restriction");
		restrictionBT.addActionListener(new RestrictionBTActionListener(this));
		gbc.gridx = 0;
		gbc.gridy = 3;
		toolBar.add(restrictionBT, gbc);
		
		MyButton recommendationsBT = new MyButton("Help");
		recommendationsBT.addActionListener(new RecommendationsBTActionListener(this));
		gbc.gridx = 0;
		gbc.gridy = 4;
		toolBar.add(recommendationsBT, gbc);
	}
	
	protected JScrollPane setScrollPane() {
		JScrollPane scrollPane = new JScrollPane();
		scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		GridBagConstraints gbc_scrollPane = new GridBagConstraints();
		gbc_scrollPane.gridheight = 3;
		gbc_scrollPane.insets = new Insets(0, 0, 5, 5);
		gbc_scrollPane.fill = GridBagConstraints.BOTH;
		gbc_scrollPane.gridx = 1;
		gbc_scrollPane.gridy = 0;
		add(scrollPane, gbc_scrollPane);
		return scrollPane;
	}
	
	protected void createTree(JScrollPane scrollPane) {
		this.top = new DefaultMutableTreeNode("Thing");
		createTreeNodes(this.top, this.parent.getOwlClassesList());
		this.treeModel = new DefaultTreeModel(this.top);

		this.tree = new JTree(treeModel);
		tree.setEditable(true);
		tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		tree.setShowsRootHandles(true);
		tree.addMouseListener(new TreeMouseListener(this));
		scrollPane.setViewportView(tree);
	}
	
	protected void initialize() {
		this.setPanelLayout();
		
		JToolBar toolBar = this.setToolBar();
		this.setToolBarButtons(toolBar);
		
		JScrollPane scrollPane = this.setScrollPane();
		this.createTree(scrollPane);
		
		JScrollPane disjoinClassSP = new JScrollPane();
		disjoinClassSP.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		disjoinClassSP.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		disjoinClassSP.setViewportBorder(new TitledBorder(null, "Disjoin CLasses", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		GridBagConstraints gbc_disjoinClassSP = new GridBagConstraints();
		gbc_disjoinClassSP.insets = new Insets(0, 0, 5, 0);
		gbc_disjoinClassSP.fill = GridBagConstraints.BOTH;
		gbc_disjoinClassSP.gridx = 2;
		gbc_disjoinClassSP.gridy = 0;
		add(disjoinClassSP, gbc_disjoinClassSP);
		
		disjoinPanel = new JPanel(new GridBagLayout());
		disjoinClassSP.setViewportView(disjoinPanel);
		
		JScrollPane restrictionSP = new JScrollPane();
		restrictionSP.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		restrictionSP.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		restrictionSP.setViewportBorder(new TitledBorder(null, "Restrictions", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		GridBagConstraints gbc_restrictionSP = new GridBagConstraints();
		gbc_restrictionSP.gridheight = 2;
		gbc_restrictionSP.insets = new Insets(0, 0, 5, 0);
		gbc_restrictionSP.fill = GridBagConstraints.BOTH;
		gbc_restrictionSP.gridx = 2;
		gbc_restrictionSP.gridy = 1;
		add(restrictionSP, gbc_restrictionSP);
		
		restrictionPanel = new JPanel(new GridBagLayout());
		restrictionSP.setViewportView(restrictionPanel);
	}
	
	private void fillDisjoinPanel(MyOWLClass clazz) {
		this.disjoinMapper = new HashMap<MyDeleteBT, MyOWLClass>(); //clear hashmap
		this.disjoinPanel.removeAll(); //clear panel 
		List<MyOWLClass> disjoinList = clazz.getDisjoinClasses();
		Iterator<MyOWLClass> iter = disjoinList.iterator();
		int y = 0;
		while(iter.hasNext()) {
			MyOWLClass o = iter.next();
			
			GridBagConstraints gbc = new GridBagConstraints();
			gbc.insets = new Insets(0, 0, 5, 20);
			gbc.anchor = GridBagConstraints.LINE_START;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.gridx = 0;
			gbc.gridy = y;
			this.disjoinPanel.add(new JLabel(o.getName()), gbc);
			
			MyDeleteBT button = new MyDeleteBT();
			button.setActionCommand("Disjoin");
			button.addActionListener(new RemoveFromPanelActionListener(this));
			gbc.gridx = 1;
			gbc.gridy = y;
			this.disjoinPanel.add(button, gbc);
			
			disjoinMapper.put(button, o); // map owlclass to button
			y++;
		}
		this.disjoinPanel.repaint();
		this.disjoinPanel.revalidate();
	}
	
	private void fillRestrictionPanel(MyOWLClass clazz) {
		this.restrictionMapper = new HashMap<MyDeleteBT, OWLRestriction>(); //clear hashmap
		this.restrictionPanel.removeAll(); //clear panel 
		List<OWLRestriction> restrioctionList = clazz.getRestrictions();
		Iterator<OWLRestriction> iter = restrioctionList.iterator();
		int y = 0;
		while(iter.hasNext()) {
			OWLRestriction o = iter.next();
			
			GridBagConstraints gbc = new GridBagConstraints();
			gbc.insets = new Insets(0, 0, 5, 20);
			gbc.anchor = GridBagConstraints.LINE_START;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.gridx = 0;
			gbc.gridy = y;
			this.restrictionPanel.add(new JLabel(o.getObject().getName()), gbc);
			
			gbc.gridx = 1;
			gbc.gridy = y;
			this.restrictionPanel.add(new JLabel(o.getClazz().getName()), gbc);			
			
			MyDeleteBT button = new MyDeleteBT();
			button.setActionCommand("Restriction");
			button.addActionListener(new RemoveFromPanelActionListener(this));
			gbc.gridx = 2;
			gbc.gridy = y;
			this.restrictionPanel.add(button, gbc);
			
			restrictionMapper.put(button, o); // map button to restriction
			y++;
		}
		this.restrictionPanel.repaint();
		this.restrictionPanel.revalidate();
	}
	
	private void createTreeNodes(DefaultMutableTreeNode top, List<? extends OWLComponent> classes) {
		DefaultMutableTreeNode clazz = null;
		for(OWLComponent cl : classes) {
			clazz = new DefaultMutableTreeNode(cl.getName());
			top.add(clazz);
			if(cl.hasSubComponent()) {
				this.createTreeNodes(clazz, cl.getSubComponents());
			}
		}
	}
	
	private String removeCurrentNode() {
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		TreePath currentSelection = tree.getSelectionPath();
		if (currentSelection != null) {
			DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent());
			MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
			if (parent != null) {
				treeModel.removeNodeFromParent(currentNode);
				return currentNode.getUserObject().toString();
			}
		}

		// Either there was no selection, or the root was selected.
		toolkit.beep();
		return "";
	}
	
	private String addNewClass(String child) {
		DefaultMutableTreeNode parentNode = null;
		TreePath parentPath = tree.getSelectionPath();

		if (parentPath == null) {
			parentNode = this.top;
		} else {
			parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent());
		}
		DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child);
		this.treeModel.insertNodeInto(childNode, parentNode, parentNode.getChildCount());
		return parentNode.getUserObject().toString();
	}
	
	private void removeClassFromList(String nodeToRemove, List<? extends OWLComponent> classes) {
		Iterator<? extends OWLComponent> iter = classes.iterator();
		while(iter.hasNext()) {
			OWLComponent o = iter.next();
			if(o.getName().equals(nodeToRemove)) {
				iter.remove();
				return;
			}
			if(o.hasSubComponent()) {
				this.removeClassFromList(nodeToRemove, o.getSubComponents());
			}
		}
	}
	
	private void addClassToList(String parentNodeName, String childName, List<? extends OWLComponent> classes) {
		if("Thing".equals(parentNodeName)) {
			this.parent.getOwlClassesList().add(new MyOWLClass(childName));
			return;
		}
		Iterator<? extends OWLComponent> iter = classes.iterator();
		while(iter.hasNext()) {
			OWLComponent o = iter.next();
			if(o.getName().equals(parentNodeName)) {
				o.getSubComponents().add(new MyOWLClass(childName));
				return;
			}
			if(o.hasSubComponent()) {
				this.addClassToList(parentNodeName, childName, o.getSubComponents());
			}
		}
	}
	
	//map disjoin classes to selected class
	private void addDisjoinClassesToClass(String selectedClass, List<MyOWLClass> disjoin) {
		MyOWLClass clazz = (MyOWLClass) this.findOWLComponentByName(selectedClass, this.parent.getOwlClassesList());
		for(MyOWLClass o : disjoin) {
			clazz.getDisjoinClasses().add(o);
		}
	}
	
	private void addRestrictionToClass(String selectedClass, OWLRestriction restriction) {
		MyOWLClass clazz = (MyOWLClass) this.findOWLComponentByName(selectedClass, this.parent.getOwlClassesList());
		clazz.getRestrictions().add(restriction);
	}
	
	private OWLComponent findOWLComponentByName(String componentName, List<? extends OWLComponent> classList) {
		OWLComponent result = null;
		Iterator<? extends OWLComponent> iter = classList.iterator();
		while(iter.hasNext()) {
			OWLComponent o = iter.next();
			if(componentName.equals(o.getName())) {
				result = o;
			} else if(o.hasSubComponent()) {
				result = this.findOWLComponentByName(componentName, o.getSubComponents());
			}
		}
		return result;
	}
	
	private class AddBTActionListener implements ActionListener {
		private ClazzPanel p;
		public AddBTActionListener(ClazzPanel p) {
			this.p = p;
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			AddInputDialog dialog = new AddInputDialog("Add new class");
			String className = dialog.getOutput();
			if(className != null && !"".equals(className)) {
				String parentName = p.addNewClass(className);
				if(parentName != null) {
					p.addClassToList(parentName, className, p.parent.getOwlClassesList());
				}
			}
		}
	}
	
	private class RemoveBTActionListener implements ActionListener {
		private ClazzPanel p;
		public RemoveBTActionListener(ClazzPanel p) {
			this.p = p;
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			String removedNode = p.removeCurrentNode();
			if("".equals(removedNode)) {
				return;
			}
			p.removeClassFromList(removedNode, p.parent.getOwlClassesList());
		}
	}
	
	private class DisjoinBTActionListener implements ActionListener {
		private ClazzPanel p;
		public DisjoinBTActionListener(ClazzPanel p) {
			this.p = p;
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			Toolkit toolkit = Toolkit.getDefaultToolkit();
			TreePath currentSelection = tree.getSelectionPath();
			String selectedClass = null;
			if (currentSelection != null) {
				DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent());
				MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
				if (parent != null) {
					selectedClass = currentNode.getUserObject().toString();
				}
				if(selectedClass != null && !"".equals(selectedClass)) {
					SelectDomainDialog dialog = new SelectDomainDialog(p.parent.getOwlClassesList()); //TODO zrefaktorovat meno pretoze sa to nepouziva len na volbu domain
					p.addDisjoinClassesToClass(selectedClass, dialog.getOutputClasses());
					MyOWLClass clazz = (MyOWLClass) p.findOWLComponentByName(selectedClass, p.parent.getOwlClassesList());
					p.fillDisjoinPanel(clazz);
				} else {
					toolkit.beep();
				}
			}
		}
	}
	
	private class RestrictionBTActionListener implements ActionListener {
		private ClazzPanel p;
		public RestrictionBTActionListener(ClazzPanel p) {
			this.p = p;
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			
			
			Toolkit toolkit = Toolkit.getDefaultToolkit();
			TreePath currentSelection = tree.getSelectionPath();
			String selectedClassName = null;
			if (currentSelection != null) {
				DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent());
				MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
				if (parent != null) {
					selectedClassName = currentNode.getUserObject().toString();
				}
				if(selectedClassName != null && !"".equals(selectedClassName)) {
					CreateRestrictionDialog dialog = new CreateRestrictionDialog(p.parent.getObjectPropertyList(), p.parent.getOwlClassesList());
					if(dialog.getOutputClass() != null && dialog.getOutputObjProperty() != null && dialog.getOutpuValue() != null) {
						MyOWLClass clazz = (MyOWLClass) p.findOWLComponentByName(dialog.getOutputClass(), p.parent.getOwlClassesList());
						MyOWLObjectProperty objProp = (MyOWLObjectProperty) p.findOWLComponentByName(dialog.getOutputObjProperty(), p.parent.getObjectPropertyList());
						String values = dialog.getOutpuValue();
						OWLRestriction restriction = new OWLRestriction(objProp, clazz, values);
						p.addRestrictionToClass(selectedClassName, restriction);
						MyOWLClass selectedClass = (MyOWLClass) p.findOWLComponentByName(selectedClassName, p.parent.getOwlClassesList());
						p.fillRestrictionPanel(selectedClass);
					}
				} else {
					toolkit.beep();
				}
			}
		}
		
	}
	
	private class RecommendationsBTActionListener implements ActionListener {
		private ClazzPanel p;
		private List<String> classesNames;
		
		public RecommendationsBTActionListener(ClazzPanel p) {
			this.p = p;
			this.classesNames = new ArrayList<String>();
		}
		
		private void fillClassesNames(List<? extends OWLComponent> classes) {
			Iterator<? extends OWLComponent> iter = classes.iterator();
			while(iter.hasNext()) {
				MyOWLClass c = (MyOWLClass)iter.next();
				this.classesNames.add(c.getName());
				if(c.hasSubComponent()) {
					this.fillClassesNames(c.getSubComponents());
				}
			}
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			DataManipulator manipulator = new DBRatingManipulator(this.p.parent.getMainWindow().getDBController());
			this.classesNames = new ArrayList<String>();
			this.fillClassesNames(p.parent.getOwlClassesList());
			try {
				List<String> reco = ((DBRatingManipulator)manipulator).getRecommendations(classesNames, EnumUtils.ATTR_TYPE_CLASS);
				RecommendationsDialog rdialog = new RecommendationsDialog(reco);
				List<String> selectedRecommendations = rdialog.getSelectedRecommendations();
				if(selectedRecommendations.size() > 0) {
					for(String s : selectedRecommendations) {
						String parentName = p.addNewClass(s);
						if(parentName != null) {
							p.addClassToList(parentName, s, p.parent.getOwlClassesList());
						}
						p.parent.getUsedRecommendations().add(s); //add used recommendations to list
					}
				}
			} catch (SQLException e1) {
				logger.error("Error during creating recommendations");
				e1.printStackTrace();
			}
		}
	}
	
	private class TreeMouseListener implements MouseListener {
		private ClazzPanel p;
		public TreeMouseListener(ClazzPanel p) {
			this.p = p;
		}
		
		@Override
		public void mouseClicked(MouseEvent e) {
			TreePath currentSelection = p.tree.getPathForLocation(e.getX(), e.getY());
			if(currentSelection != null) {
				DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent());
				MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
				if (parent != null) {
					MyOWLClass clazz = (MyOWLClass)p.findOWLComponentByName(currentNode.getUserObject().toString(), p.parent.getOwlClassesList());
					if(clazz != null) {
						p.fillDisjoinPanel(clazz);
						p.fillRestrictionPanel(clazz);
					}
				}
			}
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {}

		@Override
		public void mouseExited(MouseEvent arg0) {}

		@Override
		public void mousePressed(MouseEvent arg0) {}

		@Override
		public void mouseReleased(MouseEvent arg0) {}
	}
	
	private class RemoveFromPanelActionListener implements ActionListener {
		private ClazzPanel p;
		public RemoveFromPanelActionListener(ClazzPanel p) {
			this.p = p;
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			TreePath currentSelection = tree.getSelectionPath();
			String selectedClassName = null;
			if (currentSelection != null) {
				DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent());
				MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
				if (parent != null) {
					selectedClassName = currentNode.getUserObject().toString();
				}
			}
			if(selectedClassName != null && !"".equals(selectedClassName)) {
				MyOWLClass cla = (MyOWLClass)p.findOWLComponentByName(selectedClassName, p.parent.getOwlClassesList());
				if("Restriction".equals(e.getActionCommand())) {
					cla.getRestrictions().remove(p.restrictionMapper.get((MyDeleteBT)e.getSource()));
					p.fillRestrictionPanel(cla);
					logger.debug("Restriction removed!");
				} 
				if("Disjoin".equals(e.getActionCommand())) {
					cla.getDisjoinClasses().remove(p.disjoinMapper.get((JButton)e.getSource()));
					p.fillDisjoinPanel(cla);
					logger.debug("Disjoin removed!");
				}
			}
		}
	}
}
